#include "Die.h"
#include "Manager.h"

#include <algorithm>
#include <cstdio>

#include <libdwarf/dwarf.h>
#include <pthread.h>

namespace dwarf {

class SubprogramCompare {
public:
	bool operator()(const Subprogram &_x, const Subprogram &_y) const {
		return _x.getLowPc() < _y.getLowPc();
	}

	bool operator()(const Subprogram &_x, const void *const &_y) const {
		return _x.getLowPc() < _y;
	}

	bool operator()(const void *const &_x, const Subprogram &_y) const {
		return _x < _y.getLowPc();
	}
};

Manager &Manager::getInstance() {
	static Manager *pointer = NULL;
	if (pointer == NULL) {
		static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
		pthread_mutex_lock(&mutex);
		if (pointer == NULL) {
			static Manager instance;
			pointer = &instance;
		}
		pthread_mutex_unlock(&mutex);
	}
	return *pointer;
}

Manager::Manager()
		: debugs_(),
		  subprograms_() {
}

Manager::~Manager() {
}

bool Manager::addDebug(const char *_file) {
	bool rtn;
	Debug debug(rtn, _file);
	if (!rtn) {
		fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
		return false;
	}
	while (true) {
		Dwarf_Error error;
		Dwarf_Unsigned headerLength;
		Dwarf_Half versionStamp;
		Dwarf_Unsigned abbrevOffset;
		Dwarf_Half addressSize;
		Dwarf_Unsigned nextHeader;
		int rtn1 = dwarf_next_cu_header(debug.getData(), &headerLength, &versionStamp, &abbrevOffset, &addressSize, &nextHeader, &error);
		if (rtn1 == DW_DLV_ERROR) {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			return false;
		}
		else if (rtn1 == DW_DLV_NO_ENTRY) {
			debugs_.push_back(debug);
			return true;
		}
		RaiiDie siblingDie = Die::getSibling(debug.getData(), NULL);
		if (siblingDie.getPointer() == NULL) {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			return false;
		}
		if (!addSubprograms(debug, siblingDie.getPointer())) {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			return false;
		}
	}
}

const Subprogram *Manager::findSubprogram(const void *_pc) const {
	std::vector<Subprogram>::const_iterator iterator = std::upper_bound(subprograms_.begin(), subprograms_.end(), _pc, SubprogramCompare());
	if (iterator == subprograms_.begin()) {
		fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
		return NULL;
	}
	const Subprogram &subprogram = *--iterator;
	if (_pc >= subprogram.getHighPc()) {
		fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
		return NULL;
	}
	return &subprogram;
}

bool Manager::addSubprograms(Debug &_debug, Dwarf_Die _die) {
	Dwarf_Half tag;
	int rtn = Die::getTag(_die, tag);
	if (rtn == DW_DLV_ERROR) {
		fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
		return false;
	}
	else if (rtn == DW_DLV_OK) {
		if (tag == DW_TAG_subprogram) {
			bool rtn1;
			Subprogram subprogram(rtn1, _debug.getData(), _die);
			if (rtn1) {
				// TODO
				std::vector<Subprogram>::iterator iterator = std::upper_bound(subprograms_.begin(), subprograms_.end(), subprogram, SubprogramCompare());
				subprograms_.insert(iterator, subprogram);
			}
		}
	}
	for (RaiiDie childDie = Die::getChild(_debug.getData(), _die); childDie.getPointer() != NULL; childDie.next()) {
		if (!addSubprograms(_debug, childDie.getPointer())) {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			return false;
		}
	}
	return true;
}

}
